<html>
    <head>
        <title>EF Core 集成</title>
    </head>
    <body>
        <script>
            //  EF Core 集成

                /*
                    本文档解释了如何将EF Core作为ORM提供程序集成到基于ABP的应用程序以及如何配置它。
                */

                /*
                    一、安装

                            "Volo.Abp.EntityFrameworkCore" 是EF Core集成的主要 NuGet包。

                            将其安装到你的项目中（对于分层应用程序，安装到你的数据/基础设施层）：

                                abp add-package Volo.Abp.EntityFrameworkCore

                            注意：更好的办法，你可以直接下载预装了EF Core的启动模板。


                            1、选择数据库管理系统

                                    Entity Framework Core支持各种数据库管理系统（请参阅全部）。

                                    ABP和本文档不依赖于任何特定的DBMS。

                                    如果你正在创建一个可重用的应用程序模块，请避免依赖于特定的DBMS包。但是，在最终的应用程序中，你最终会选择一个DBMS。

                                    请参阅切换到另一个DBMS for Entity Framework Core文档以了解如何切换DBMS。
                */

                /*
                    二、创建数据库上下文  Creating  DbContext


                            1、如何创建DbContext类

                                    你的DbContext类应该派生自AbpDbContext<T>，如下所示：

                                                using Microsoft.EntityFrameworkCore;
                                                using Volo.Abp.EntityFrameworkCore;

                                                namespace MyCompany.MyProject
                                                {
                                                    public class MyDbContext : AbpDbContext<MyDbContext>
                                                    {
                                                        //...your DbSet properties here

                                                        public MyDbContext(DbContextOptions<MyDbContext> options)
                                                            : base(options)
                                                        {
                                                        }
                                                    }
                                                }

                            2、关于EF Core Fluent API 映射

                                    应用程序启动模板已配置为使用EF Core Fluent 配置API将你的实体映射到你的数据库表。

                                    你仍然可以对实体的属性使用数据注释属性（如[Required]），而ABP留档通常遵循流畅映射API方法。这取决于你。

                                    ABP有一些基本实体类和约定（请参阅实体文档），它提供了一些有用的扩展方法来配置从基本实体类继承的属性。

                                    ①、ConfigureByConvention方法

                                            ConfigureByConvention()是为实体配置所有基本属性和约定的主要扩展方法。

                                            因此，在Fluent API 的映射代码中为所有实体调用此方法是最佳实践。

                                            eg:  假设你有一个Book实体派生自AggregateRoot<Guid>基类：

                                                    public class Book : AuditedAggregateRoot<Guid>
                                                    {
                                                        public string Name { get; set; }
                                                    }

                                            你可以覆盖DbContext中的OnModelCreating方法并配置映射，如下所示：

                                                    protected override void OnModelCreating(ModelBuilder builder)
                                                    {
                                                        //  一定记得要调用基类的OnModelCreating方法，否则你将丢失ABP的默认配置。
                                                        base.OnModelCreating(builder);

                                                        builder.Entity<Book>(b =>
                                                        {
                                                            b.ToTable("Books");

                                                            // 按照约定配置基本属性，必须这样做，才能实现AuditedAggregateRoot基类的属性配置。
                                                            b.ConfigureByConvention();

                                                            // 配置特定于Book的属性
                                                            b.Property(x => x.Name).IsRequired().HasMaxLength(128);
                                                        });
                                                    }

                                                    这里调用b.ConfigureByConvention()对于正确配置基本属性很重要。
                                                
                                                    你可以在此处配置Name属性，也可以使用数据注释特性（请参阅EF Core文档）。

                                            虽然有许多扩展方法可以配置你的基本属性，但ConfigureByConvention()在必要时在内部调用它们。所以，调用它就足够了

                        
                         3、设置连接字符串

                                   如果你的应用程序中有"多个数据库"， 则可以使用[ConnectionStringName]属性为DbContext配置连接字符串名称。

                                   例如：
                                            [ConnectionStringName("MySecondConnString")]
                                            public class MyDbContext : AbpDbContext<MyDbContext>
                                            {

                                            }

                                    如果不配置，则使用Default连接字符串。

                                    如果你配置了特定的连接字符串名称，但未在应用程序配置中定义此连接字符串名称，则它会回退到Default连接字符串（有关详细信息，请参阅连接字符串文档）。


                        4、ABP数据库上下文选项类  AbpDbContextOptions

                                    AbpDbContextOptions用于配置DbContext选项。

                                    当你使用ABP的应用程序启动模板创建新解决方案时，你将看到一个简单的配置（在EntityFrameworkCore集成项目的模块类中），
                                    
                                    如下所示：

                                            Configure<AbpDbContextOptions>(options =>
                                            {
                                                options.UseSqlServer();
                                            });

                                    该配置将应用程序的所有DbContext的默认DBMS配置为SQL服务器。

                                    该配置是一个简写方式，可以使用以下代码块完成：

                                            Configure<AbpDbContextOptions>(options =>
                                            {
                                                options.Configure(opts =>
                                                {
                                                    opts.UseSqlServer();
                                                });
                                            });

                                    options.Configure(...)方法可以配置更多的选项。 如，你可以设置DbContextOptions（EF Core的本机选项），如下所示：
                                    
                                            Configure<AbpDbContextOptions>(options =>
                                            {
                                                options.Configure(opts =>
                                                {
                                                    opts.DbContextOptions.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
                                                });
                                            });


                                    为DbContext的ConfigureConventions和OnModelCreating方法添加操作，如下所示：

                                            options.DefaultConventionAction = (dbContext, builder) =>
                                            {
                                                // This action is called for ConfigureConventions method of all DbContexts.
                                            };

                                            options.ConfigureConventions<YourDbContext>((dbContext, builder) =>
                                            {
                                                // This action is called for ConfigureConventions method of specific DbContext.
                                            });

                                            options.DefaultOnModelCreatingAction = (dbContext, builder) =>
                                            {
                                                // This action is called for OnModelCreating method of all DbContexts.
                                            };

                                            options.ConfigureOnModelCreating<YourDbContext>((dbContext, builder) =>
                                            {
                                                // This action is called for OnModelCreating method of specific DbContext.
                                            });


                                    如果你有一个DbContext或者你有多个DbContext，但想为所有人使用相同的DBMS和配置，你可以保持原样。

                                    但是，如果你需要配置不同的DBMS或为特定的DbContext自定义配置，你可以如下所示指定它：

                                            Configure<AbpDbContextOptions>(options =>
                                            {
                                                // 所有 DbContext 的木人配置
                                                options.Configure(opts =>
                                                {
                                                    opts.UseSqlServer();
                                                });

                                                // 针对特定 DbContext 的自定义设置
                                                options.Configure<MyOtherDbContext>(opts =>
                                                {
                                                    opts.UseMySQL();
                                                });
                                            });

                                    请参阅切换到另一个DBMS for Entity Framework Core文档以了解如何配置DBMS。
                                    
                */

                /*
                    三、将DbContext 注册到依赖注入



                            1、如何注册DbContext到IOC

                                        在模块中使用AddAbpDbContext方法为依赖注入系统注册你的DbContext类。

                                                using Microsoft.Extensions.DependencyInjection;
                                                using Volo.Abp.EntityFrameworkCore;
                                                using Volo.Abp.Modularity;

                                                namespace MyCompany.MyProject
                                                {
                                                    [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
                                                    public class MyModule : AbpModule
                                                    {
                                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            // 注册DbContext
                                                            context.Services.AddAbpDbContext<MyDbContext>();

                                                            //...
                                                        }
                                                    }
                                                }      
                                                    
                            2、添加默认存储库

                                        ABP可以自动为DbContext中的实体创建默认的通用存储库。只需在注册时使用AddDefaultRepositories()选项：

                                                services.AddAbpDbContext<MyDbContext>(options =>
                                                {
                                                    options.AddDefaultRepositories();
                                                });

                                        默认情况下，这将为每个聚合根实体（派生自AggregateRoot的类）创建一个存储库。

                                        如果你也想为其他实体创建存储库，请将includeAllEntities设置为true：

                                                services.AddAbpDbContext<MyDbContext>(options =>
                                                {
                                                    options.AddDefaultRepositories(includeAllEntities: true);
                                                });
                            
                                        然后，你可以在你的服务中注入并使用IRepository<TEntity, TPrimaryKey>。假设你有一个Book实体，其主键为Guid：

                                                public class Book : AggregateRoot<Guid>
                                                {
                                                    public string Name { get; set; }

                                                    public BookType Type { get; set; }
                                                }

                                        (BookType在这里是一个简单的enum并不重要）并且你想在域服务中创建一个新的Book实体:

                                                public class BookManager : DomainService
                                                {
                                                    private readonly IRepository<Book, Guid> _bookRepository;

                                                    // 注入默认仓储
                                                    public BookManager(IRepository<Book, Guid> bookRepository)
                                                    {
                                                        _bookRepository = bookRepository;
                                                    }

                                                    public async Task<Book> CreateBook(string name, BookType type)
                                                    {
                                                        Check.NotNullOrWhiteSpace(name, nameof(name));

                                                        var book = new Book
                                                        {
                                                            Id = GuidGenerator.Create(),
                                                            Name = name,
                                                            Type = type
                                                        };

                                                        // 使用标准的存储方法
                                                        await _bookRepository.InsertAsync(book);

                                                        return book;
                                                    }
                                                }

                            3、添加自定义存储库

                                       默认通用存储库在大多数情况下足够强大（因为它们实现了IQueryable）。

                                       但是，你可能需要创建一个自定义存储库来添加你自己的存储库方法。假设你想按类型删除所有书籍。

                                       建议为你的自定义存储库定义一个接口：

                                                public interface IBookRepository : IRepository<Book, Guid>
                                                {
                                                    Task DeleteBooksByType(BookType type);
                                                }
                                        你通常希望从IRepository派生以继承标准存储库方法（虽然，你不必这样做）。

                                        存储库接口在分层应用程序的 "领域层" 中定义。。在"基础设施层"中实现（EntityFrameworkCore启动模板中的项目）。

                                        IBookRepository接口的示例实现：

                                                public class BookRepository
                                                    : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
                                                {
                                                    public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
                                                        : base(dbContextProvider)
                                                    {
                                                    }

                                                    public async Task DeleteBooksByType(BookType type)
                                                    {
                                                        var dbContext = await GetDbContextAsync();
                                                        await dbContext.Database.ExecuteSqlRawAsync(
                                                            $"DELETE FROM Books WHERE Type = {(int)type}"
                                                        );
                                                    }
                                                }

                                        现在，可以注入IBookRepository并在需要时使用DeleteBooksByType方法。

                                    ★、覆盖默认通用仓储

                                            即使你创建了自定义存储库，你仍然可以注入默认的泛型存储库（IRepository<Book, Guid>对于本例）。默认存储库实现不会使用你创建的类。

                                            如果你想用自定义存储库替换默认存储库实现，请在 AddAbpDbContext 选项中执行：

                                                        context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
                                                        {
                                                            options.AddDefaultRepositories();

                                                            // 指定仓储的实现类
                                                            options.AddRepository<Book, BookRepository>();
                                                        });

                                            当你想要覆盖基本存储库方法以对其进行自定义时，这一点尤其重要。

                                            例如，你可能希望覆盖DeleteAsync方法以更有效的方式删除特定实体：

                                                        public async override Task DeleteAsync(
                                                            Guid id,
                                                            bool autoSave = false,
                                                            CancellationToken cancellationToken = default)
                                                        {
                                                            //TODO: Custom implementation of the delete method
                                                        }
                */

                /*
                    四、加载关联实体 (导航属性实体)

                                假设你有一个Order，其中包含OrderLines的集合，并且OrderLine具有Order的导航属性：

                                        using System;
                                        using System.Collections.Generic;
                                        using System.Collections.ObjectModel;
                                        using Volo.Abp.Auditing;
                                        using Volo.Abp.Domain.Entities;

                                        namespace MyCrm
                                        {
                                            public class Order : AggregateRoot<Guid>, IHasCreationTime
                                            {
                                                public Guid CustomerId { get; set; }
                                                public DateTime CreationTime { get; set; }

                                                public ICollection<OrderLine> Lines { get; set; } //集合导航属性

                                                public Order()
                                                {
                                                    Lines = new Collection<OrderLine>();
                                                }
                                            }

                                            public class OrderLine : Entity<Guid>
                                            {
                                                public Order Order { get; set; } // 导航属性
                                                public Guid OrderId { get; set; }

                                                public Guid ProductId { get; set; }
                                                public int Count { get; set; }
                                                public double UnitPrice { get; set; }
                                            }
                                        }

                                并定义了数据库映射如下所示：

                                        builder.Entity<Order>(b =>
                                        {
                                            b.ToTable("Orders");
                                            b.ConfigureByConvention();

                                            // 声明关系
                                            b.HasMany(x => x.Lines)
                                                .WithOne(x => x.Order)
                                                .HasForeignKey(x => x.OrderId)
                                                .IsRequired();
                                        });

                                        builder.Entity<OrderLine>(b =>
                                        {
                                            b.ToTable("OrderLines");
                                            b.ConfigureByConvention();
                                        });

                                当你查询Order时，你可能希望在单个查询中包含所有OrderLine，或者你可能希望稍后按需加载它们。


                                1、急切加载(Eager Loading)  / 加载详情 (Load With Details)

                                       当你想在查询实体时加载相关实体时，你有不同的选项:
                                        
                                            ①、Repository.WithDetails

                                                    IRepository.WithDetailsAsync(...)可用于通过包含一个关系集合/属性来获取IQueryable<T>

                                                    eg:

                                                            using System;
                                                            using System.Linq;
                                                            using System.Threading.Tasks;
                                                            using Volo.Abp.Domain.Repositories;
                                                            using Volo.Abp.Domain.Services;

                                                            namespace AbpDemo.Orders
                                                            {
                                                                public class OrderManager : DomainService
                                                                {
                                                                    private readonly IRepository<Order, Guid> _orderRepository;

                                                                    public OrderManager(IRepository<Order, Guid> orderRepository)
                                                                    {
                                                                        _orderRepository = orderRepository;
                                                                    }

                                                                    public async Task TestWithDetails(Guid id)
                                                                    {
                                                                        // 等于.net core 中的 .Include(x => x.Lines)，返回一个 IQueryable<T>
                                                                        var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);

                                                                        // 执行Linq过滤器
                                                                        var query = queryable.Where(x => x.Id == id);

                                                                        // 执行查询
                                                                        var order = await AsyncExecuter.FirstOrDefaultAsync(query);
                                                                    }
                                                                }
                                                            }

                                                    AsyncExecuter用于执行异步LINQ扩展，而不依赖于EF Core.

                                                    如果你将EF Core NuGet包引用添加到你的项目中，那么你可以直接使用await query.FirstOrDefaultAsync()。

                                                    但是，这次你依赖于域层中的EF Core。

                                                    eg: 获取订单列表及其行

                                                            public async Task TestWithDetails()
                                                            {
                                                                // Get a IQueryable<T> by including sub collections
                                                                var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);

                                                                // Execute the query and get the result
                                                                var orders = await AsyncExecuter.ToListAsync(queryable);
                                                            }

                                                    ★、DefaultWithDetailsFunc 

                                                            如果你没有将任何表达式传递给WithDetailsAsync方法，则它使用你提供的DefaultWithDetailsFunc选项包含所有详细信息。

                                                            你可以在EntityFrameworkCore项目中模块的ConfigureServices方法中为实体配置DefaultWithDetailsFunc。

                                                            eg：在查询Order时包含Lines

                                                                    Configure<AbpEntityOptions>(options =>
                                                                    {
                                                                        options.Entity<Order>(orderOptions =>
                                                                        {
                                                                            orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
                                                                        });
                                                                    });

                                                            然后你可以在没有任何参数的情况下使用WithDetails：

                                                                     public async Task TestWithDetails()
                                                                    {
                                                                        // IQueryable<T> by including all sub collections
                                                                        var queryable = await _orderRepository.WithDetailsAsync();

                                                                        //Execute the query and get the result
                                                                        var orders = await AsyncExecuter.ToListAsync(queryable);
                                                                    }   

                                            ②、 存储库的Get/Find 方法
                                            
                                                        一些标准的Repository方法具有可选的includeDetails参数:

                                                                GetAsync和FindAsync获取includeDetails，默认值为true。

                                                                GetListAsync和GetPagedListAsync获取includeDetails，默认值为false。

                                                        这意味着，这些方法默认返回单个实体包含详细信息，而列表返回方法默认不包含详细信息.
                                                        
                                                        你可以显式传递includeDetails来更改行为。

                                                        ★、这些方法使用上面解释的DefaultWithDetailsFunc选项。

                                                        eg： 获取包含详细信息的订单

                                                                public async Task TestWithDetails(Guid id)
                                                                {
                                                                    var order = await _orderRepository.GetAsync(id);
                                                                }  

                                                        eg: 获取没有详细信息的订单

                                                                public async Task TestWithoutDetails(Guid id)
                                                                {
                                                                    var order = await _orderRepository.GetAsync(id, includeDetails: false);
                                                                }

                                                        eg: 获取包含详细信息的实体列表

                                                                public async Task TestWithDetails()
                                                                {
                                                                    var orders = await _orderRepository.GetListAsync(includeDetails: true);
                                                                }
                                            ③、替代品

                                                    存储库模式试图封装EF Core，因此你的选择有限。如果你需要高级场景，你可以遵循其中一个选项：

                                                                （1）创建自定义存储库方法并使用完整的EF Core API。

                                                                （2）引用项目中的Volo.Abp.EntityFrameworkCore包。这样，你可以在代码中直接使用Include和ThenInclude




                                2、显示加载/延迟加载   Explicit / Lazy Loading

                                            如果你在查询实体时不包含关系，并且以后需要访问导航属性或集合，你有不同的选择。
                                            

                                        ①、显式加载

                                                存储库提供 "EnsurePropertyLoadedAsync"和 "EnsureCollectionLoadedAsync"扩展方法来显式加载导航属性或子集合。

                                                eg: 需要时加载订单的行

                                                        public async Task TestWithDetails(Guid id)
                                                        {
                                                            var order = await _orderRepository.GetAsync(id, includeDetails: false);
                                                            // 此时order.Lines为null

                                                            await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines);
                                                            //order.Lines 已经被填充了值，不再是null了
                                                        } 
                                                            
                                                 EnsurePropertyLoadedAsync和EnsureCollectionLoadedAsync方法什么也不做。因此，多次调用没有问题。


                                        ②、延迟加载  Lazy Loading with Proxies

                                                在某些情况下可能无法显式加载，尤其是当你没有对Repository或DbContext的引用时。

                                                延迟加载是EF Core的一项功能，可在你第一次访问它时加载相关属性/集合。

                                                启用延迟加载:

                                                        (1)将Microsoft. EntityFrameworkCore.Proxies包安装到你的项目中（通常安装到EF Core集成项目中）

                                                        (2)为DbContext配置UseLazyLoadingProxies（在EF Core项目中模块的ConfigureServices方法中）。
                                                        
                                                                示例：
                                                                    Configure<AbpDbContextOptions>(options =>
                                                                    {
                                                                        options.PreConfigure<MyCrmDbContext>(opts =>
                                                                        {
                                                                            opts.DbContextOptions.UseLazyLoadingProxies(); // 启用懒加载
                                                                        });

                                                                        options.UseSqlServer();
                                                                    });

                                                        (3)、把你的导航属性设置为virtual

                                                                eg:
                                                                    public virtual ICollection<OrderLine> Lines { get; set; } //virtual 集合导航属性
                                                                    public virtual Order Order { get; set; } //virtual 导航属性

                                                启用延迟加载并获取实体后，你可以自由访问导航属性和集合：

                                                        public async Task TestWithDetails(Guid id)
                                                        {
                                                            var order = await _orderRepository.GetAsync(id);
                                                            //此时 order.Lines 是空的 

                                                            var lines = order.Lines;
                                                            // 现在  order.Lines 已经被填充了
                                                        }

                                                每当你访问属性/集合时，EF Core都会自动执行附加查询以从数据库中加载属性/集合。


                */

                /*
                    五、只读存储 Read-Only Repositories

                            ABP提供只读的repository接口（IReadOnlyRepository<...>或IReadOnlyBasicRepository<...>）

                            来明确表示你的目的是查询数据，但不是更改它。

                            如果是这样，你可以将这些接口注入到你的服务中。


                            Entity Framework Core只读存储库实现使用EF Core的无跟踪功能。这意味着EF Core更改跟踪器不会跟踪从存储库返回的实体，因为预计你不会更新从只读存储库查询的实体。

                            如果你需要跟踪实体，你仍然可以在LINQ表达式上使用AsTrack（）扩展方法，

                            或在存储库对象上使用EnableTracking()扩展方法（请参阅本文档中的启用/禁用更改跟踪部分）。

                            仅当存储库对象被注入只读存储库接口之一（IReadOnlyRepository<...>或IReadOnlyBasicRepository<...>）时，此行为才有效。

                            如果你注入了标准存储库（例如IRepository<...>）然后将其强制转换到只读存储库接口，则不起作用。
                            
                */


                /*
                    六、启用/禁用更改跟踪   Enabling / Disabling the Change Tracking

                            除了只读存储库之外，ABP还允许手动控制查询对象的更改跟踪行为。请参阅存储库留档的启用/禁用更改跟踪部分以了解如何使用它。

                            https://abp.io/docs/latest/framework/architecture/domain-driven-design/repositories
                */


                /*
                    七、访问EF Core API

                            在大多数情况下，你希望将EF Core API隐藏在存储库后面（这是存储库模式的主要目的）。

                            但是，如果你想通过存储库访问DbContext实例，你可以使用 GetDbContext() 或 GetDbSet() 扩展方法。
                            
                            例子：

                                public async Task TestAsync()
                                {
                                    // 获取数据库上下文实例
                                    var dbContext = await _orderRepository.GetDbContextAsync();

                                    // 获取存储对应的数据库表的DbSet实例
                                    var dbSet = await _orderRepository.GetDbSetAsync();
                                    //var dbSet = dbContext.Set<Order>(); // 另外一种获取DbSet的方式

                                }

                            GetDbContextAsync返回DbContext引用而不是BookStoreDbContext。如果需要，你可以强制转换它。但是，在大多数情况下，你不需要它。

                            备注：
                                    
                                重要提示：你必须从要访问DbContext的项目中引用Volo.Abp.EntityFrameworkCore包。这会破坏封装，但在这种情况下这就是你想要的。
                */

                /*
                     八、额外属性 和 对象扩展管理器  Extra Properties & Object Extension Manager

                                Extra Properties系统允许你为实现IHasExtraProperties接口的实体设置/获取动态属性。

                                当你想将自定义属性添加到依赖的应用程序模块中定义的实体时，当你将模块用作包引用时，它特别有用。

                                默认情况下，实体的所有额外属性都存储为数据库中的单个JSON对象。

                                实体扩展系统允许你将所需的额外属性存储在相关数据库表的单独字段中。
                                
                                有关额外属性和实体扩展系统的更多信息，请参阅以下文档：

                                    ①、自定义应用程序模块：扩展实体   https://abp.io/docs/latest/framework/architecture/modularity/extending/customizing-application-modules-extending-entities

                                    ②、Entities 实体   https://abp.io/docs/latest/framework/architecture/domain-driven-design/entities


                            1、ObjectExtensionManager.Instance 对象扩展管理器实例

                                    ObjectExtensionManager实现单例模式，因此需要使用静态ObjectExtensionManager.Instance来执行所有操作。

                                    ObjectExtensionManager.Instance 用来访问对象扩展管理器的唯一实例。

                            2、MapEfCoreProperty  （映射额外属性）

                                    MapEfCoreProperty是一种快捷扩展方法，用于定义实体的扩展属性并映射到数据库。

                                    eg:  将Title属性（数据库字段）添加到IdentityRole实体：

                                         ObjectExtensionManager.Instance
                                                                .MapEfCoreProperty<IdentityRole, string>(
                                                                    "Title",
                                                                    (entityBuilder, propertyBuilder) =>
                                                                    {
                                                                        propertyBuilder.HasMaxLength(64);
                                                                    }
                                                                );

                            3、MapEfCoreEntity  （映射某个实体类型的额外属性）

                                    MapEfCoreEntity是配置Entity的快捷扩展方法。、

                                    eg： 设置IdentityRole实体的Name属性最大长度为200

                                            ObjectExtensionManager.Instance
                                                                    .MapEfCoreEntity<IdentityRole>(builder =>
                                                                    {
                                                                        builder.As<EntityTypeBuilder<IdentityRole>>().Property(x => x.Name).HasMaxLength(200);
                                                                    });


                            4、MapEfCoreDbContext  (映射某个DbContext上的实体、属性配置)

                                    MapEfCoreDbContext是配置DbContext的快捷扩展方法。

                                    eg：设置IdentityDbContext 中的IdentityRole实体的Name属性最大长度为200：

                                            ObjectExtensionManager.Instance.MapEfCoreDbContext<IdentityDbContext>(b =>
                                            {
                                                b.Entity<IdentityRole>().Property(x => x.Name).HasMaxLength(200);
                                            });


                            如果相关模块已经实现了此功能（如下所述），则将新属性添加到模型或更改DbContext/Entity配置。

                            然后你需要运行标准的Add-Migration和Update-Database命令来更新你的数据库以添加新字段。


                                        

                            5、ConfigureEfCoreEntity、ApplyObjectExtensionMappings 和TryConfigureObjectExtensions

                                    如果你正在构建可重用模块并希望允许应用程序开发人员向你的实体添加属性，
                                    
                                    你可以在实体映射中使用ConfigureEfCoreEntity、ApplyObjectExtensionMappings和TryConfigureObjectExtensions扩展方法。
                                
                                    eg：
                                        public static class QADbContextModelCreatingExtensions
                                        {
                                            public static void ConfigureQA(
                                                this ModelBuilder builder,
                                                Action<QAModelBuilderConfigurationOptions> optionsAction = null)
                                            {
                                                Check.NotNull(builder, nameof(builder));

                                                var options = new QAModelBuilderConfigurationOptions(
                                                    QADatabaseDbProperties.DbTablePrefix,
                                                    QADatabaseDbProperties.DbSchema
                                                );

                                                optionsAction?.Invoke(options);

                                                builder.Entity<QA_Question>(b =>
                                                {
                                                    b.ToTable(options.TablePrefix + "Questions", options.Schema);
                                                    b.ConfigureByConvention();
                                                    //...

                                                    //在 build操作后调用ApplyObjectExtensionMappings方法
                                                    b.ApplyObjectExtensionMappings();
                                                });

                                                //...

                                                //Call this in the end of ConfigureQA.
                                                builder.TryConfigureObjectExtensions<QADbContext>();
                                            }
                                        }

                                  如果你调用ConfigureByConvention()扩展方法（如本例中的b.ConfigureByConvention()），ABP会在内部调用ConfigureObjectExtensions和ConfigureEfCoreEntity方法。

                                  使用ConfigureByConvention()方法是的最佳实践，因为它还按照约定为基本属性配置数据库映射。
                                                    
                */

                /*
                     九、高级主题

                                1、控制多租户

                                                如果你的解决方案是多租户，租户可能有单独的数据库，你的解决方案中有多个DbContext类，
                                                
                                                并且你的某些DbContext类应该是主机端通用的，不是针对某个租户，而是所有租户都通用的，

                                                建议在你的DbContext类上添加[IgnoreMultiTenancy]属性。

                                                在这种情况下，ABP保证相关的DbContext始终使用主机连接字符串，即使你在租户上下文中。

                                                eg：

                                                    [IgnoreMultiTenancy]  // 忽略多租户，说明该DbContext不区分租户，始终使用主机连接字符串。
                                                    public class MyDbContext : AbpDbContext<MyDbContext>
                                                    {
                                                        ...
                                                    }

                                                ★、如果DbContext中的任何一个实体可以持久化在租户数据库中，请不要使用[IgnoreMultiTenancy]属性。

                                            备注：

                                                当你使用存储库时，ABP已经使用了主机数据库，因为实体没有实现IMultiTenant接口。

                                                因此，如果你使用存储库来处理数据库，大多数时候你不需要[IgnoreMultiTenancy]属性。

                                2、设置默认存储库

                                           默认情况下，默认通用存储库由EfCoreRepository类实现。你可以创建自己的实现并将其用于所有默认存储库实现。
                                           
                                           首先，像这样定义你的默认存储库类：

                                                    // 用于复合主键实体
                                                    public class MyRepositoryBase<TEntity>
                                                        : EfCoreRepository<BookStoreDbContext, TEntity>
                                                        where TEntity : class, IEntity  // 泛型约束
                                                    {
                                                        public MyRepositoryBase(IDbContextProvider<BookStoreDbContext> dbContextProvider)
                                                            : base(dbContextProvider)
                                                        {
                                                        }
                                                    }

                                                    // 用于单个主键的实体
                                                    public class MyRepositoryBase<TEntity, TKey>
                                                        : EfCoreRepository<BookStoreDbContext, TEntity, TKey>
                                                        where TEntity : class, IEntity<TKey> // 反应约束
                                                    {
                                                        public MyRepositoryBase(IDbContextProvider<BookStoreDbContext> dbContextProvider)
                                                            : base(dbContextProvider)
                                                        {
                                                        }
                                                    }

                                                    第一个用于具有复合主键的实体，第二个用于具有单个主键的实体。

                                            建议从EfCoreRepository类继承并在需要时覆盖方法。否则，你将不得不手动实现所有标准存储库方法。

                                            现在，你可以使用SetDefaultRepositoryClasses选项：

                                                    context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
                                                    {
                                                        options.SetDefaultRepositoryClasses(  // 设置默认存储库的实现类
                                                            typeof(MyRepositoryBase<,>),
                                                            typeof(MyRepositoryBase<>)
                                                        );

                                                        //...
                                                    });
                                        
                                3、为默认存储库设置基本DbContext类或接口

                                            果你的DbContext继承自另一个DbContext或实现了一个接口，你可以将该基类或接口用作默认存储库的DbContext。
                                            
                                            示例：

                                                public interface IBookStoreDbContext : IEfCoreDbContext
                                                {
                                                    DbSet<Book> Books { get; }
                                                }
                                            IBookStoreDbContext由BookStoreDbContext类实现。然后你可以使用AddDefaultRepositories的通用重载：

                                                context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
                                                {
                                                    options.AddDefaultRepositories<IBookStoreDbContext>();
                                                    //...
                                                });

                                            现在，你的自定义BookRepository也可以使用IBookStoreDbContext接口：

                                                public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository
                                                {
                                                    //...
                                                }

                                            将接口用于DbContext的一个优点是它可以被另一个实现替换。

                                
                                4、替换其他DbContextes

                                            一旦你正确定义和使用了DbContext的接口，那么任何其他实现都可以使用以下方式来替换它：

                                            ①、 [ReplaceDbContext ]特性，替换 DbContext

                                                        [ReplaceDbContext(typeof(IBookStoreDbContext))]  // 替换DbContext
                                                        public class OtherDbContext : AbpDbContext<OtherDbContext>, IBookStoreDbContext
                                                        {
                                                            //...
                                                        }

                                            ②、ReplaceDbContext 方法替换默认的 DbContext

                                                        context.Services.AddAbpDbContext<OtherDbContext>(options =>
                                                        {
                                                            //...
                                                            options.ReplaceDbContext<IBookStoreDbContext>();
                                                        });

                                                        在此示例中，OtherDbContext实现IBookStoreDbContext。

                                                        此功能允许你在开发时拥有多个DbContext（每个模块一个），但在运行时拥有单个DbContext（实现所有DbContext的所有接口）。

                                            ③、用多租户替换

                                                        也可以基于多租户端替换DbContext。

                                                        ReplaceDbContext特性 和ReplaceDbContext方法可以获得默认值为MultiTenancySides的MultiTenancySides选项。

                                                    eg: 仅为租户替换DbContext，使用[ReplaceDbContext] 特性：

                                                                [ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]

                                                    eg: 仅为主机端替换DbContext，使用ReplaceDbContext方法

                                                                options.ReplaceDbContext<IBookStoreDbContext>(MultiTenancySides.Host);


                                5、拆分查询

                                        ABP默认全局启用拆分查询以获得更好的性能。你可以根据需要进行更改。

                                        eg:
                                            Configure<AbpDbContextOptions>(options =>
                                            {
                                                options.UseSqlServer(optionsBuilder =>
                                                {
                                                    optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery);
                                                });
                                            });

                                            什么是拆分查询？  看EF Core Pdf


                                6、自定义批量操作

                                         如果你有更好的逻辑或使用外部库进行批量操作，你可以通过实现IEfCoreBulkOperationProvider来覆盖该逻辑。

                                         你可以使用下面的示例模板：

                                                public class MyCustomEfCoreBulkOperationProvider
                                                    : IEfCoreBulkOperationProvider, ITransientDependency
                                                {
                                                    public async Task DeleteManyAsync<TDbContext, TEntity>(
                                                        IEfCoreRepository<TEntity> repository,
                                                        IEnumerable<TEntity> entities,
                                                        bool autoSave,
                                                        CancellationToken cancellationToken)
                                                        where TDbContext : IEfCoreDbContext
                                                        where TEntity : class, IEntity
                                                    {
                                                        // Your logic here.
                                                    }

                                                    public async Task InsertManyAsync<TDbContext, TEntity>(
                                                        IEfCoreRepository<TEntity> repository,
                                                        IEnumerable<TEntity> entities,
                                                        bool autoSave,
                                                        CancellationToken cancellationToken)
                                                        where TDbContext : IEfCoreDbContext
                                                        where TEntity : class, IEntity
                                                    {
                                                        // Your logic here.
                                                    }

                                                    public async Task UpdateManyAsync<TDbContext, TEntity>(
                                                        IEfCoreRepository<TEntity> repository,
                                                        IEnumerable<TEntity> entities,
                                                        bool autoSave,
                                                        CancellationToken cancellationToken)
                                                        where TDbContext : IEfCoreDbContext
                                                        where TEntity : class, IEntity
                                                    {
                                                        // Your logic here.
                                                    }
                                                }

                */
        </script>
    </body>
</html>